package org.flashamaphone.tones
{
	import flash.utils.ByteArray;
	
	import org.flashamaphone.buffer.SoundConstants;
	import org.flashamaphone.debug.Debugger;
	import org.flashamaphone.tones.ITone;
	import org.flashamaphone.voices.IVoice;
	import org.flashamaphone.voices.SimpleWaveform;
	
	/** 
	 * A voiced tone implements the standard note lifecycle - attack,
	 * decay, sustain, and release
	 */
	public class Tone implements ITone
	{
		public static const ATTACK_PHASE:String = "attackPhase";
		public static const DECAY_PHASE:String = "decayPhase";
		public static const SUSTAIN_PHASE:String = "sustainPhase";
		public static const RELEASE_PHASE:String = "releasePhase";
		
		/** attack duration (milliseconds) */
		protected var _attackDuration:int = 100;
		
		/** decay duration (milliseconds) */
		protected var _decayDuration:int = 100;
		
		/** release duration (milliseconds) */
		protected var _releaseDuration:int = 100;
		
		/** debug mode */
		protected var _debug:Boolean = false;
		
		/** current phase */
		protected var _phase:String = ATTACK_PHASE;
		
		/** frequency of tone */
		protected var _frequency:Number = 440;
		
		/** duration of tone */
		protected var _duration:Number = 1;
		
		/** bytes streamed so far */
		protected var _bytesStreamed:int = 0;
		
		/** release bytes streamed so far */
		protected var _releaseBytesStreamed:int = 0;
		
		/** precached attack + decay */
		protected var _precachedAttackDecay:ByteArray;
		
		/** precached release */
		protected var _precachedRelease:ByteArray;
		
		/** voice */
		protected var _voice:IVoice = new SimpleWaveform();
		
		/**
		 * constructor
		 */
		public function Tone() {
			super();
		}
		
		/**
		 * debug getter
		 * 
		 * @return debug
		 */
		public function get debug():Boolean {
			return _debug;
		}
		
		/**
		 * debug setter
		 * 
		 * @param value
		 */
		public function set debug(value:Boolean):void {
			_debug = value;
		}
		
		/**
		 * frequency getter
		 * 
		 * @return frequency
		 */
		public function get frequency():Number {
			return _frequency;
		}
		
		/**
		 * frequency setter
		 * 
		 * @param frequency
		 */
		public function set frequency(value:Number):void {
			_frequency = value;
		}
		
		/**
		 * frequency duration
		 * 
		 * @return duration
		 */
		public function get duration():Number {
			return _duration;
		}
		
		/**
		 * frequency duration
		 * 
		 * @param duration
		 */
		public function set duration(value:Number):void {
			_duration = value;
		}
		
		/**
		 * voice getter
		 * 
		 * @return voice
		 */
		public function get voice():IVoice {
			return _voice;
		}
		
		/**
		 * voice setter
		 * 
		 * @param voice
		 */
		public function set voice(value:IVoice):void {
			_voice = value;
		}
		
	
		/** 
		 * getter for sustain duration (duration minus all the phases)
		 * 
		 * @return milliseconds
		 */
		public function get sustainDuration():int {
			var durationInMilliseconds:int = this.duration * 1000;
			var otherPhases:int = attackDuration + decayDuration + releaseDuration;
			var sus:int = durationInMilliseconds - otherPhases;
			if (sus > 0) {
				return sus;
			} else {
				return 0;
			}
		}
		
		/** 
		 * get tone byte array
		 * 
		 * @return tone byte array
		 */
		public function get bytes():ByteArray {
			var byts:ByteArray = new ByteArray;
			byts.writeBytes(this.attack);
			byts.writeBytes(this.decay);
			byts.writeBytes(this.sustain);
			byts.writeBytes(this.release);
			return byts;
		}
		
		/**
		 * get live bytestream x bytes at a time depending on phase
		 * 
		 * @param number of bytes to get
		 * @return bytes
		 */
		public function stream(numBytes:int):ByteArray {
			// precache attack and decay, so we can grab exactly the amount of bytes we need
			// over however many passes this takes
			if (!_precachedAttackDecay) {
				_precachedAttackDecay = new ByteArray();
				_precachedRelease = new ByteArray();
				_precachedAttackDecay.writeBytes( this.attack );
				_precachedAttackDecay.writeBytes( this.decay );
				_precachedRelease.writeBytes( this.release );
			}
			
			var stream:ByteArray = new ByteArray();
			var offset:int = _bytesStreamed;
			var len:int = numBytes;

			// burn through precached attack + decay byte array before moving onto sustain
			if ( phase == ATTACK_PHASE || phase == DECAY_PHASE ) {
				if (offset + len > _precachedAttackDecay.length) {
					len = 0; // zero out to have length ignored and just get the rest of the cache
				}
				stream.writeBytes( _precachedAttackDecay, offset, len );
				if ( _bytesStreamed + stream.length >= this.attackDuration * (SoundConstants.SAMPLING_RATE_MILLISECONDS) * 8 ) {
					phase = DECAY_PHASE;
				}
				
				if ( _bytesStreamed + stream.length >= _precachedAttackDecay.length ) {
					phase = SUSTAIN_PHASE;
				}
			}
			
			// sustain phase goes on indefinitely
			len = numBytes - stream.length + 1;
			if ( phase == SUSTAIN_PHASE ) {
				// fill the remaining stream with sustain
				var duration:Number = (len/8)/(SoundConstants.SAMPLING_RATE_MILLISECONDS);
				stream.writeBytes( voice.sustain(this.frequency, duration) );
			}
			
			// burn through precached release byte array before moving onto sustain
			len = numBytes - stream.length;
			offset = _releaseBytesStreamed;
			if ( phase == RELEASE_PHASE ) {
				if (offset < len ) {
					stream.writeBytes( _precachedRelease, offset, len);
				}
				_releaseBytesStreamed += stream.length;
			}

			_bytesStreamed += stream.length;
			
			if(debug) {
				Debugger.debug(stream);
			}
			return stream;
		}
		
		/**
		 * reset byte stream of this note
		 */
		public function resetByteStream():void {
			_bytesStreamed = 0;
			_releaseBytesStreamed = 0;
			_precachedAttackDecay = null;
			_precachedRelease = null;
			phase = ATTACK_PHASE;
			
		}
		
		/** 
		 * get attack bytes
		 */
		public function get attack():ByteArray {
			return voice.attack(this.frequency, this.attackDuration);
		}
		
		/** 
		 * get attack duration
		 * 
		 * @return duration
		 */
		public function get attackDuration():int {
			return _attackDuration;
		}
		
		/** 
		 * set attack duration
		 * 
		 * @param duration
		 */
		public function set attackDuration(value:int):void {
			_attackDuration = value;
		}
		
		
		/** 
		 * get decay bytes
		 */
		public function get decay():ByteArray {
			return voice.decay(this.frequency, this.decayDuration);
		}
		
		/** 
		 * get decay duration
		 * 
		 * @return duration
		 */
		public function get decayDuration():int {
			return _decayDuration;
		}
		
		/** 
		 * set decay duration
		 * 
		 * @param duration
		 */
		public function set decayDuration(value:int):void {
			_decayDuration = value;
		}
		
		/** 
		 * get sustain bytes
		 */
		public function get sustain():ByteArray {
			return voice.sustain(this.frequency, this.sustainDuration);
		}
		
		/** 
		 * get release bytes
		 */
		public function get release():ByteArray {
			return voice.release(this.frequency, this.releaseDuration);
		}
		
		/** 
		 * get release duration
		 * 
		 * @return duration
		 */
		public function get releaseDuration():int {
			return _releaseDuration;
		}
		
		/** 
		 * set release duration
		 * 
		 * @param duration
		 */
		public function set releaseDuration(value:int):void {
			_releaseDuration = value;
		}
		
		/**
		 * phase getter
		 * 
		 * @return phase
		 */
		public function get phase():String {
			return _phase;
		}
		
		/**
		 * phase setter
		 * 
		 * @param phase
		 */
		public function set phase(value:String):void {
			_phase = value;
		}
		
		/**
		 * clone
		 * 
		 * @param tone
		 * @return tone
		 */
		public function clone():ITone {
			var tone:Tone = new Tone();
			tone.phase = this.phase;
			tone.releaseDuration = this.releaseDuration;
			tone.decayDuration = this.decayDuration;
			tone.attackDuration =  this.attackDuration;
			tone.duration = this.duration;
			tone.voice = this.voice;
			tone.frequency = this.frequency;
			return tone;
		}
	}
}